<?php

class Am_Paysystem_AuthorizeCim extends Am_Paysystem_CreditCard
{
    const PLUGIN_STATUS = self::STATUS_PRODUCTION;
    const PLUGIN_DATE = '$Date$';
    const PLUGIN_REVISION = '4.2.13.4d4e034';

    const LIVE_URL = 'https://api.authorize.net/xml/v1/request.api';
    const SANDBOX_URL = 'https://apitest.authorize.net/xml/v1/request.api';
    
    const MERCHANT_CUSTOMER_ID_KEY = 'cim-merchant-customer-id-';
    const USER_PROFILE_KEY = 'authorize_cim_user_profile_id';
    const PAYMENT_PROFILE_KEY = 'authorize_cim_payment_profile_id';

    protected $defaultTitle = "Pay with your Credit Card";
    protected $defaultDescription  = "accepts all major credit cards";
    
    protected $_pciDssNotRequired = true;
    
    public function getRecurringType()
    {
        return self::REPORTS_REBILL;
    }
    
    public function getSupportedCurrencies()
    {
        return array('USD', 'EUR', 'GBP', 'CAD');
    }
    
    public function isConfigured()
    {
        return strlen($this->getConfig('login')) && strlen($this->getConfig('tkey'));
    }

    public function _doBill(Invoice $invoice, $doFirst, CcRecord $cc, Am_Paysystem_Result $result)
    {
        if (!$this->getConfig('hosted'))
        {
            $user = $invoice->getUser();
            if ($cc->user_id != $user->pk())
                throw new Am_Exception_Paysystem("Assertion failed: cc.user_id != user.user_id");

            // will be stored only if cc# or expiration changed
            $this->storeCreditCard($cc, $result);
            if (!$result->isSuccess())
                return;

            $user->refresh();
            // we have both profile id and payment id, run the necessary transaction now if amount > 0
            $result->reset();
        }
        $this->_doTheBill($invoice, $doFirst, $cc, $result);
    }
    
    /* process invoice after creating payment profile */
    public function _doTheBill(Invoice $invoice, $doFirst, CcRecord $cc, Am_Paysystem_Result $result)
    {
        if ($doFirst && (doubleval($invoice->first_total) <= 0))
        { // free trial
            $tr = new Am_Paysystem_Transaction_Free($this);
            $tr->setInvoice($invoice);
            $tr->process();
            $result->setSuccess($tr);
        } else {
            $tr = new Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfileTransaction($this, $invoice, $doFirst);
            $tr->run($result);
        }
    }
    
    public function _initSetupForm(Am_Form_Setup $form)
    {
        $form->addAdvCheckbox('hosted')->setLabel("Use Hosted Version (recommended)\n".
            "this option allows you to display credit card input right on your website\n".
            "(as a popup) and in the same time it does not require PCI DSS compliance\n");
           
        $form->addText("login")->setLabel(array('API Login ID', '
            can be obtained from the same page as Transaction Key (see below)'));
        $form->addText("tkey")->setLabel(array('Transaction Key', 
            "
The transaction key is generated by the system 
and can be obtained from Merchant Interface.
To obtain the transaction key from the Merchant
Interface:
  *  Log into the Merchant Interface
  *  Select [Settings] from the Main Menu
  *  Click on [API Login ID and Transaction Key] in the Security section
  *  Type in the answer to the secret question configured on setup
  *  Click Submit
"));
        $form->addSelect("validationMode")
            ->setLabel("Validation mode for creating customer profile" . "\n" .
                "Validation mode allows you to generate a test transaction at the time
you create a customer payment profile. In Test Mode, only field validation is performed.
In Live Mode, a transaction is generated and submitted to the processor with the
amount of $0.01. If successful, the transaction is immediately voided. When a value of \"none\"
is submitted, no additional validation is performed.
")
            ->loadOptions(array(
                'liveMode' => 'liveMode',
                'testMode' => 'testMode',
                'none' => 'none'
            ));
        $form->addAdvCheckbox("testing")->setLabel("Test Mode Enabled" . "\n" .
            "The Test Mode requires a separate developer test account, which can be set up by filling out the following form: <a target=\"_blank\" href=\"http://developer.authorize.net/testaccount\">http://developer.authorize.net/testaccount</a>");
        
    }
    
    public function getMerchantCustomerId($userOrId)
    {
        if ($userOrId instanceof User) $userOrId = $userOrId->pk();
        return $userOrId . '-' . substr($this->getDi()->app->getSiteHash('cim'), 0, 5);
    }
    
    public function storeCreditCard(CcRecord $cc, Am_Paysystem_Result $result)
    {
        $user = $this->getDi()->userTable->load($cc->user_id);
        $profileId = $user->data()->get(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY);
        if ($this->invoice)
        { // to link log records with current invoice
            $invoice = $this->invoice;
        } else { // updating credit card info?
            $invoice = $this->getDi()->invoiceRecord;
            $invoice->invoice_id = 0;
            $invoice->user_id = $user->pk();
        }

        // compare stored cc for that user may be we don't need to refresh?
        if ($profileId && ($cc->cc_number != '0000000000000000'))
        {
            $storedCc = $this->getDi()->ccRecordTable->findFirstByUserId($user->pk());
            if ($storedCc && (($storedCc->cc != $cc->maskCc($cc->cc_number)) || ($storedCc->cc_expire != $cc->cc_expire)))
            {
                $user->data()
                    ->set(self::USER_PROFILE_KEY, null)
                    ->set(self::PAYMENT_PROFILE_KEY, null)
                    ->update();
                $deleteTr = new Am_Paysystem_Transaction_AuthorizeCim_DeleteCustomerProfile($this, $invoice, $profileId);
                $deleteTr->run($res = new Am_Paysystem_Result);
                $profileId = null;
            }
        }
        
        if (!$profileId)
        {
            try {
                $tr = new Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfile($this, $invoice, $cc);
                $tr->run($result);
                if (!$result->isSuccess())
                    return;
                $user->data()->set(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY, $tr->getProfileId())->update();
                $user->data()->set(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY, $tr->getPaymentId())->update();
            } catch (Am_Exception_Paysystem $e) {
                $result->setFailed($e->getPublicError());
                return false;
            }
        }
        /// 
        $cc->cc = $cc->maskCc(@$cc->cc_number);
        $cc->cc_number = '0000000000000000';
        if ($cc->pk())
            $cc->update();
        else
            $cc->replace();
        $result->setSuccess();
    }
    
    public function getReadme()
    {
        return <<<CUT
Authorize.Net CIM 
---------------------------------------------------
  
The biggest advantage of this plugin is that altough credit card info
is entered on your website, it will be stored on Auth.Net secure servers
so recurring billing is secure and you do not have to store cc info on your
own website.

You need to enable CIM service in authorize.net 
(Tools -> Customer Information Manager -> Sign Up Now)
This is a paid service.
             

1. Enable and configure plugin in aMember CP -> Setup -> Plugins

2. You NEED to use external cron with this plugins
    (See Setup/Configuration -> Advanced)

CUT;
    }
    function isRefundable(InvoicePayment $payment)
    {
        return true;
    }
    
    function processRefund(InvoicePayment $payment, Am_Paysystem_Result $result, $amount)
    {
        $trans = new Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfileTransactionRefund($this, $payment->getInvoice(), $payment, $amount);
        $trans->run($result);
    }
    function getHostedProfilePageToken()
    {
        $user = $this->getDi()->userTable->load($this->invoice->user_id);
        $profileId = $user->data()->get(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY);
        $invoice = $this->invoice;

        if (!$profileId)
        {
            $tr = new Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfile($this, $invoice);
            $result = new Am_Paysystem_Result();
            $tr->run($result);
            if (!$result->isSuccess())
                throw new Am_Exception_Paysystem("Failed Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfile " . $result->getLastError());
            $user->data()->set(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY, $tr->getProfileId())->update();
        }
        $tr = new Am_Paysystem_Transaction_AuthorizeCim_GetHostedProfilePageRequest($this, $invoice, $profileId);
        $result = new Am_Paysystem_Result();
        $tr->run($result);
        if (!$tr->getUniqId())
            throw new Am_Exception_Paysystem("Could not get hosted-profile-page-token from authorize.net - connection problem");
        return $tr->getUniqId();
    }
    
    // use custom controller 
    protected function createController(Am_Request $request, Zend_Controller_Response_Http $response, array $invokeArgs)
    {
        if ($this->getConfig('hosted'))
            return new Am_Controller_CreditCard_AuthorizeNet($request, $response, $invokeArgs);
        else
            return parent::createController($request, $response, $invokeArgs);
    }
    
    public function directAction(Am_Request $request, Zend_Controller_Response_Http $response, array $invokeArgs)
    {
        if ($request->getActionName() == 'iframe')
        {
            $p = $this->createController($request, $response, $invokeArgs);
            $p->setPlugin($this);
            $p->run();
            return;
        }
        parent::directAction($request, $response, $invokeArgs);
    }
    
    public function getUpdateCcLink($user)
    {
        if ($user->data()->get(self::PAYMENT_PROFILE_KEY))
            return $this->getPluginUrl('update');
    }
}

abstract class Am_Paysystem_Transaction_AuthorizeCim extends Am_Paysystem_Transaction_CreditCard
{
    protected $apiName = null;
    protected $aimResponse = null;
    
    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $doFirst = true)
    {
        parent::__construct($plugin, $invoice, $plugin->createHttpRequest(), $doFirst);
        $this->request->setHeader('Content-type', 'text/xml');
        $this->request->setBody($this->createXml($this->apiName)->asXml());
        $this->request->setMethod(Am_HttpRequest::METHOD_POST);
        $this->request->setUrl(!$this->plugin->getConfig('testing') ? 
            Am_Paysystem_AuthorizeCim::LIVE_URL : 
            Am_Paysystem_AuthorizeCim::SANDBOX_URL);
    }
    /** @return SimpleXmlElement */
    protected function createXml($name)
    {
        $xml = new SimpleXmlElement('<'.$name.'/>');
        $xml['xmlns'] = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd';
        $xml->merchantAuthentication->name = $this->plugin->getConfig('login');
        $xml->merchantAuthentication->transactionKey = $this->plugin->getConfig('tkey');
        return $xml;
    }
    public function parseResponse()
    {
        $body = trim($this->response->getBody());
        $body = str_replace('xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"', '', $body);
        $this->xml = new SimpleXMLElement($body);
    }
    public function parseAimString($string)
    {
        $vars = explode(',', $string);
        $this->aimResponse = new stdclass;
        if (count($vars) < 10) 
        {
            $this->aimResponse->error_message = "Unrecognized response from Authorize.Net: " . $string;
            return false;
        }
        // Set all fields
        $this->aimResponse->aimResponse_code        = $vars[0];
        $this->aimResponse->aimResponse_subcode     = $vars[1];
        $this->aimResponse->aimResponse_reason_code = $vars[2];
        $this->aimResponse->aimResponse_reason_text = $vars[3];
        $this->aimResponse->authorization_code   = $vars[4];
        $this->aimResponse->avs_response         = $vars[5];
        $this->aimResponse->transaction_id       = $vars[6];
        $this->aimResponse->invoice_number       = $vars[7];
        $this->aimResponse->description          = $vars[8];
        $this->aimResponse->amount               = $vars[9];
        $this->aimResponse->method               = $vars[10];
        $this->aimResponse->transaction_type     = $vars[11];
        $this->aimResponse->customer_id          = $vars[12];
        $this->aimResponse->first_name           = $vars[13];
        $this->aimResponse->last_name            = $vars[14];
        $this->aimResponse->company              = $vars[15];
        $this->aimResponse->address              = $vars[16];
        $this->aimResponse->city                 = $vars[17];
        $this->aimResponse->state                = $vars[18];
        $this->aimResponse->zip_code             = $vars[19];
        $this->aimResponse->country              = $vars[20];
        $this->aimResponse->phone                = $vars[21];
        $this->aimResponse->fax                  = $vars[22];
        $this->aimResponse->email_address        = $vars[23];
        $this->aimResponse->ship_to_first_name   = $vars[24];
        $this->aimResponse->ship_to_last_name    = $vars[25];
        $this->aimResponse->ship_to_company      = $vars[26];
        $this->aimResponse->ship_to_address      = $vars[27];
        $this->aimResponse->ship_to_city         = $vars[28];
        $this->aimResponse->ship_to_state        = $vars[29];
        $this->aimResponse->ship_to_zip_code     = $vars[30];
        $this->aimResponse->ship_to_country      = $vars[31];
        $this->aimResponse->tax                  = $vars[32];
        $this->aimResponse->duty                 = $vars[33];
        $this->aimResponse->freight              = $vars[34];
        $this->aimResponse->tax_exempt           = $vars[35];
        $this->aimResponse->purchase_order_number= $vars[36];
        $this->aimResponse->md5_hash             = $vars[37];
        $this->aimResponse->card_code_response   = $vars[38];
        $this->aimResponse->cavv_response        = $vars[39];
        $this->aimResponse->account_number       = $vars[40];
        $this->aimResponse->card_type            = $vars[51];
        $this->aimResponse->split_tender_id      = $vars[52];
        $this->aimResponse->requested_amount     = $vars[53];
        $this->aimResponse->balance_on_card      = $vars[54];
        return true;
    }
    
}

class Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfile extends Am_Paysystem_Transaction_AuthorizeCim
{
    protected $apiName = 'createCustomerProfileRequest';
    /** @var CcRecord */
    protected $cc;
    
    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, CcRecord $cc = null)
    {
        $this->cc = $cc;
        parent::__construct($plugin, $invoice);
    }
    
    protected function createXml($name)
    {
        $xml = parent::createXml($name);
        $user = $this->invoice->getUser();
        $xml->profile->merchantCustomerId = $this->plugin->getMerchantCustomerId($this->cc ? $this->cc->user_id : $this->invoice->user_id);
        $xml->profile->description = "Username: $user->login";
        $xml->profile->email = $user->email;
        if ($this->cc)
        {
            $xml->profile->paymentProfiles->billTo->firstName = $this->cc->cc_name_f;
            $xml->profile->paymentProfiles->billTo->lastName = $this->cc->cc_name_l;
            $xml->profile->paymentProfiles->billTo->address = $this->cc->cc_street;
            $xml->profile->paymentProfiles->billTo->city = $this->cc->cc_city;
            $xml->profile->paymentProfiles->billTo->state = $this->cc->cc_state;
            $xml->profile->paymentProfiles->billTo->zip = $this->cc->cc_zip;
            $xml->profile->paymentProfiles->billTo->country = $this->cc->cc_country;
            $xml->profile->paymentProfiles->billTo->phoneNumber = $this->cc->cc_phone;
            $xml->profile->paymentProfiles->payment->creditCard->cardNumber = $this->cc->cc_number;
            $xml->profile->paymentProfiles->payment->creditCard->expirationDate = $this->cc->getExpire('20%2$02d-%1$02d');
            if (strlen($this->cc->getCvv()))
                $xml->profile->paymentProfiles->payment->creditCard->cardCode = $this->cc->getCvv();
            $xml->validationMode = $this->getPlugin()->getConfig('validationMode', 'liveMode');
        }
        return $xml;
    }
    
    public function validate()
    {
        if ($this->xml->getName() != 'createCustomerProfileResponse')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if ((string)$this->xml->messages->message->code != 'I00001')
        {
            $this->result->setFailed((string)$this->xml->messages->message->text);
            return;
        }
        $this->result->setSuccess();
        return true;
    }
    function getProfileId()
    {
        return (string)$this->xml->customerProfileId;
    }
    function getPaymentId()
    {
        return (string)$this->xml->customerPaymentProfileIdList->numericString;
    }
    public function getUniqId()
    {
        return (string)$this->xml->customerProfileId;
    }
    public function processValidated()
    {
    }
}
  
class Am_Paysystem_Transaction_AuthorizeCim_DeleteCustomerProfile extends Am_Paysystem_Transaction_AuthorizeCim
{
    protected $apiName = 'deleteCustomerProfileRequest';
    protected $profileId;
    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $profileId)
    {
        $this->profileId = $profileId;
        parent::__construct($plugin, $invoice, true);
    }
    protected function createXml($name)
    {
        $xml = parent::createXml($name);
        $xml->customerProfileId = $this->profileId;
        return $xml;
    }
    public function validate()
    {
        if ($this->xml->getName() != 'deleteCustomerProfileResponse')
        {
            $this->result->setFailed(___('Profile update transaction failed'));
            return;
        }
        if ((string)$this->xml->messages->message->code != 'I00001')
        {
            $this->result->setFailed((string)$this->xml->messages->message->text);
            return;
        }
        $this->result->setSuccess();
        return true;
    }
    public function getUniqId()
    {
        return uniqid();
    }
    public function processValidated()
    {
    }
}

class Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfileTransaction extends Am_Paysystem_Transaction_AuthorizeCim
{
    protected $apiName = 'createCustomerProfileTransactionRequest';
    /** @var object */
    protected $aimResponse;
    
    protected function createXml($name)
    {
        $xml = parent::createXml($name);
        $xml->transaction->profileTransAuthCapture->amount = 
            $this->doFirst ? $this->invoice->first_total : $this->invoice->second_total;
        $xml->transaction->profileTransAuthCapture->tax->amount = 
            $this->doFirst ? $this->invoice->first_tax : $this->invoice->second_tax;
        $xml->transaction->profileTransAuthCapture->shipping->amount = 
            $this->doFirst ? $this->invoice->first_shipping : $this->invoice->second_shipping;
        
        foreach ($this->invoice->getItems() as $item)
        {
            /* @var $item InvoiceItem */
            $line = $xml->transaction->profileTransAuthCapture->addChild('lineItems');
            $line->itemId = $item->item_id;
            $line->name = substr($item->item_title, 0, 30);
            $line->quantity = $item->qty;
            $price = $this->doFirst ? $item->first_price : $item->second_price;
            if ($price)
                $line->unitPrice = $price;
            if ($this->doFirst ? $item->first_tax : $item->second_tax)
                $line->taxable = 'true';
            else
                $line->taxable = 'false';
        }

        $user = $this->invoice->getUser();
        
        $taxAmount = $this->doFirst ? $this->invoice->first_tax : $this->invoice->second_tax;
        if ($taxAmount)
        $xml->transaction->profileTransAuthCapture->tax->amount = $taxAmount;

        $xml->transaction->profileTransAuthCapture->customerProfileId =
            $user->data()->get(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY);
        $xml->transaction->profileTransAuthCapture->customerPaymentProfileId =
            $user->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY);
        $xml->transaction->profileTransAuthCapture->order->description =
            $this->invoice->getLineDescription();
        $xml->transaction->profileTransAuthCapture->order->purchaseOrderNumber = 
            $this->invoice->public_id . '-' . $this->invoice->getPaymentsCount();
        $xml->transaction->profileTransAuthCapture->recurringBilling =
            $this->doFirst ? 'true' : 'false';
        return $xml;
    }
    public function validate()
    {
        if ($this->xml->getName() != 'createCustomerProfileTransactionResponse')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if (!$this->parseAimString($this->xml->directResponse))
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if ((string)$this->xml->messages->message->code != 'I00001')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        $this->result->setSuccess($this);
        return true;
    }
    public function getUniqId()
    {
        return $this->aimResponse->transaction_id;
    }
    public function processValidated()
    {
        $this->invoice->addPayment($this);
    }
}

class Am_Paysystem_Transaction_AuthorizeCim_CreateCustomerProfileTransactionRefund extends Am_Paysystem_Transaction_AuthorizeCim
{
    protected $apiName = 'createCustomerProfileTransactionRequest';
    /** @var object */
    protected $aimResponse;
    public $amount;
    public $transId;
    
    function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, InvoicePayment $payment, $amount)
    {
        $this->transId = $payment->receipt_id;
        $this->amount = $amount;
        parent::__construct($plugin, $invoice);
    }
    protected function createXml($name)
    {
        $xml = parent::createXml($name);
        $xml->transaction->profileTransRefund->amount = $this->amount;

        $user = $this->invoice->getUser();
        

        $xml->transaction->profileTransRefund->customerProfileId =
            $user->data()->get(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY);
        $xml->transaction->profileTransRefund->customerPaymentProfileId =
            $user->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY);
        $xml->transaction->profileTransRefund->order->description = "Refund for payment: ".$this->transId." amount: ".$this->amount;
        $xml->transaction->profileTransRefund->order->purchaseOrderNumber = 
            $this->invoice->public_id . '-RFND-' . $this->transId;
        $xml->transaction->profileTransRefund->transId = $this->transId;
        return $xml;
    }
    public function validate()
    {
        if ($this->xml->getName() != 'createCustomerProfileTransactionResponse')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if (!$this->parseAimString($this->xml->directResponse))
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if ((string)$this->xml->messages->message->code != 'I00001')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        $this->result->setSuccess($this);
        return true;
    }
    public function getUniqId()
    {
        return $this->aimResponse->transaction_id;
    }
    public function processValidated()
    {
        $this->invoice->addRefund($this, $this->transId, $this->amount);
    }
}

class Am_Paysystem_Transaction_AuthorizeCim_GetHostedProfilePageRequest extends Am_Paysystem_Transaction_AuthorizeCim
{
    protected $apiName = 'getHostedProfilePageRequest';
    /** @var object */
    protected $aimResponse;
    
    protected $profileId;
    
    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $profileId)
    {
        $this->profileId = $profileId;
        parent::__construct($plugin, $invoice, true);
    }
    
    protected function createXml($name)
    {
        $xml = parent::createXml($name);
        
        $xml->customerProfileId = $this->profileId;
        
        /*
        $xml->hostedProfileSettings->setting[0]->settingName = 'hostedProfileReturnUrl';
        $xml->hostedProfileSettings->setting[0]->settingValue = $this->plugin->getReturnUrl();
        $xml->hostedProfileSettings->setting[1]->settingName = 'hostedProfileReturnUrlText';
        $xml->hostedProfileSettings->setting[1]->settingValue = 'Return';
        
        $xml->hostedProfileSettings->setting[2]->settingName = 'hostedProfileHeadingBgColor';
        $xml->hostedProfileSettings->setting[2]->settingValue = '';
        */
        
        $xml->hostedProfileSettings->setting[0]->settingName = 'hostedProfilePageBorderVisible';
        $xml->hostedProfileSettings->setting[0]->settingValue = 'false';

        $xml->hostedProfileSettings->setting[1]->settingName = 'hostedProfileIFrameCommunicatorUrl';
        $xml->hostedProfileSettings->setting[1]->settingValue = $this->getPlugin()->getPluginUrl('iframe');

        return $xml;
    }
    public function validate()
    {
        if ($this->xml->getName() != 'getHostedProfilePageResponse')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if ((string)$this->xml->messages->message->code != 'I00001')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        $this->result->setSuccess($this);
        return true;
    }
    public function getUniqId()
    {
        return (string)$this->xml->token;;
    }
    public function processValidated()
    {
    }
}

class Am_Paysystem_Transaction_AuthorizeCim_GetCustomerProfile extends Am_Paysystem_Transaction_AuthorizeCim
{
    protected $apiName = 'getCustomerProfileRequest';
    /** @var object */
    protected $aimResponse;
    
    protected $profileId;
    
    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $profileId)
    {
        $this->profileId = $profileId;
        parent::__construct($plugin, $invoice, true);
    }
    
    protected function createXml($name)
    {
        $xml = parent::createXml($name);
        $xml->customerProfileId = $this->profileId;
        return $xml;
    }
    public function validate()
    {
        if ($this->xml->getName() != 'getCustomerProfileResponse')
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        if (!$this->getUniqId())
        {
            $this->result->setFailed(___('Payment failed'));
            return;
        }
        $this->result->setSuccess($this);
        return true;
    }
    public function getUniqId()
    {
        // fetch LAST payment profile id from customer profile
        foreach ($this->xml->profile->paymentProfiles as $x)
            $ret = (string)$x->customerPaymentProfileId;
        return $ret;
    }
    public function processValidated()
    {
    }
}


class Am_Controller_CreditCard_AuthorizeNet extends Am_Controller
{
    /** @var Am_Paysystem_AuthorizeCim */
    protected $plugin;
    /** @var Invoice */
    protected $invoice;
    
    public function setPlugin($plugin) { $this->plugin = $plugin; }
    public function setInvoice($invoice) { $this->invoice = $invoice; }
    

    protected function ccError($msg)
    {
        $this->view->content .= "<b><span class='error'>".$msg."</div></b>";
        $url = $this->_request->getRequestUri();
        $url .= (strchr($url, '?') ? '&' : '?') . 'id=' . $this->_request->get('id');
        $url = Am_Controller::escape($url);
        $this->view->content .= " <b><a href='$url'>".___('Return and try again')."</a></b>";
        $this->_response->setBody($this->view->render('layout.phtml'));
    }
    
    /**
     * Load last payment profile from customer profile
     * and save it to customer data()
     * if ok, return true, if failed, display error and return false
     */
    protected function loadLastProfile()
    {
        // fetch list of payment profiles from Auth.Net and save latest to customer
        $result = new Am_Paysystem_Result();
        $tr = new Am_Paysystem_Transaction_AuthorizeCim_GetCustomerProfile($this->plugin, $this->invoice, 
                $this->invoice->getUser()->data()->get(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY));
        $tr->run($result);
        if (!$result->isSuccess())
        {
            $this->ccError($result->getLastError());
            return false;
        }
        $this->invoice->getUser()->data()->set(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY, $tr->getUniqId());
        return true;
    }

    /**
     * Process a transaction using saved customer payment profile
     * and do either success redirect, or display error
     * @return Am_Paysystem_Result
     */
    protected function ccActionProcessAfterProfileExists()
    {
        $result = new Am_Paysystem_Result();
        $this->plugin->_doTheBill($this->invoice, true, 
            $this->getDi()->CcRecordTable->createRecord(), $result);
        if (@$result->errorCode == 'E00040') // Customer Profile ID or Customer Payment Profile ID not found
        {
            // cleanup customer payment profile
            $user = $this->invoice->getUser();
            $user->data()->set(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY, null);
            $tr = new Am_Paysystem_Transaction_AuthorizeCim_DeleteCustomerProfile($this->plugin, $this->invoice, $user->data()->set(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY));
            $tr->run(new Am_Paysystem_Result);
            $user->data()->set(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY, null);
            $user->data()->update();
        }
        return $result;
    }
    
    public function ccAction()
    {
        $this->view->title = ___('Payment Info');
        $this->view->invoice = $this->invoice;
        $this->view->content = $this->view->render('_receipt.phtml');
        
        if ($this->_request->get('result') == 'success')
        {
            if (!$this->invoice->getUser()->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY))
            {
                $ret = $this->loadLastProfile();
                // check status here?
            }
            $result = $this->ccActionProcessAfterProfileExists();
            if ($result->isSuccess())
            {
                $s = new Zend_Session_Namespace($this->plugin->getId());
                $s->setExpirationSeconds(60*30); // after 30 minutes we will reset the session
                $s->ccConfirmed = true;
                
                $this->_response->setRedirect($this->plugin->getReturnUrl());
            } else {
                $this->ccError($result->getLastError());
            }
        } else {
            if ($this->invoice->getUser()->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY))
            {
                // if we have credit card on file, we will try to use it but we
                // have to display confirmation first
                $s = new Zend_Session_Namespace($this->plugin->getId());
                $s->setExpirationSeconds(60*30); // after 30 minutes we will reset the session
                $s->ccConfirmed = !empty($s->ccConfirmed);
                switch ($this->_request->get('result'))
                {
                    case 'confirm':
                        $s->ccConfirmed = true;
                        break;
                    default:
                        if (!$s->ccConfirmed)
                            return $this->displayReuse();
                }
                if ($s->ccConfirmed)
                {
                    $result = $this->ccActionProcessAfterProfileExists();
                    if ($result->isSuccess())
                    {
                        return $this->_response->setRedirect($this->plugin->getReturnUrl());
                    }
                }
            }
            return $this->displayHostedPage($this->plugin->getCancelUrl());
        }
    }
    
    protected function displayReuse()
    {
        $result = new Am_Paysystem_Result;
        $tr = new Am_Paysystem_Transaction_AuthorizeCim_GetCustomerProfile($this->plugin, $this->invoice, 
                $this->invoice->getUser()->data()->get(Am_Paysystem_AuthorizeCim::USER_PROFILE_KEY));
        $tr->run($result);
        if (!$result->isSuccess())
            throw new Am_Exception_Paysystem("Stored customer profile not found");
        $payprofileid = $this->invoice->getUser()->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY);
        foreach ($tr->xml->profile->paymentProfiles as $pp)
        {
            if ($payprofileid == (string)$pp->customerPaymentProfileId)
            {
                $card = (string)$pp->payment->creditCard->cardNumber;
            }
        }
        if (empty($card))
            throw new Am_Exception_Paysystem("Store payment profile not found");
        
        $text = ___('Click "Continue" to pay this order using stored credit card %s', $card);
        $continue = ___('Continue');

        $action = $this->plugin->getPluginUrl('cc');
        $id = Am_Controller::escape($this->_request->get('id'));
        $action = Am_Controller::escape($action);;
        $this->view->content .= <<<CUT
<div class='am-reuse-card-confirmation'>
$text
<form method='get' action='$action'>
    <input type='hidden' name='id' value='$id' />
    <input type='hidden' name='result' value='confirm' />
    <input type='submit' value='$continue' />
</form>
</div>
   
CUT;
        $this->view->display('layout.phtml');
    }
    
    public function updateAction()
    {
        $user = $this->getDi()->auth->getUser(true);
        
        // dirty hack - load last user invoice to be used in process
        $this->invoice = $this->getDi()->invoiceTable->findFirstBy(array('user_id' => $user->pk(),
            'paysys_id' => $this->plugin->getId()), 0, 1, "invoice_id DESC");
        if (!$this->invoice)
            throw new Am_Exception_InternalError("No active invoices found, but update cc info request received?");
        $this->plugin->_setInvoice($this->invoice);
        
        $this->view->title = ___('Payment Info');
        $this->view->invoice = null;
        $this->view->content = "";
        
        if (!$user->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY))
            throw new Am_Exception_InternalError("User payment profile empty - user must never reach credit card update page!");
        
        if ($this->_request->get('result') == 'success')
            $this->_redirect(ROOT_SURL . '/member');
        
        return $this->displayHostedPage(ROOT_SURL . '/member');
    }

    protected function displayHostedPage($cancelUrl)
    {
        $id = $this->invoice->getUser()->data()->get(Am_Paysystem_AuthorizeCim::PAYMENT_PROFILE_KEY);
        if ($id)
        {
            $method = 'editPayment';
            $id = filterId($id);
        } else {
            $method = 'addPayment';
            $id = null;
        }
        
        $token = $this->plugin->getHostedProfilePageToken();
        
        $domain = $this->plugin->getConfig('testing') ? 'test' : 'secure';
        $cancelUrl = json_encode($cancelUrl);
        $popupTitle = json_encode(___('Credit Card Info'));
        $this->view->content .= <<<CUT
<div id="AuthorizeNetPopupInner" style='display:none'>
  <iframe width=440 height=480 name="iframeAuthorizeNet" id="iframeAuthorizeNet" src="about:blank" frameborder="0" scrolling="no"></iframe>
</div>
            
<form method="post" action="https://$domain.authorize.net/profile/$method" 
       id="formAuthorizeNetPopup" name="formAuthorizeNetPopup" 
       target="iframeAuthorizeNet" style=''>
    <input type="hidden" name="token" value="$token" />
    <input type="hidden" name="PaymentProfileId" value="$id" />
</form>

<script type="text/javascript">
jQuery(function () {
	if (!window.AuthorizeNetPopup) window.AuthorizeNetPopup = {};
	if (!AuthorizeNetPopup.options) AuthorizeNetPopup.options = {
		onPopupClosed: null
	};
	AuthorizeNetPopup.onReceiveCommunication = function (querystr) {
		var params = parseQueryString(querystr);
        console.log(params);
		switch(params["action"]) {
			case "successfulSave":
                jQuery("#AuthorizeNetPopupInner").dialog("close");
                var href = window.location.href;
                if (href.match(/\?/))
                    window.location.href = href + '&result=success';
                else
                    window.location.href = href + '?result=success';
				break;
			case "cancel":
                jQuery("#AuthorizeNetPopupInner").dialog("close");
                window.location.href = $cancelUrl;
				break;
			case "resizeWindow":
				var w = parseInt(params["width"]);
				var h = parseInt(params["height"]);
				var ifrm = document.getElementById("iframeAuthorizeNet");
				ifrm.style.width = w.toString() + "px";
				ifrm.style.height = h.toString() + "px";
				break;
		}
	};
	function parseQueryString(str) {
		var vars = [];
		var arr = str.split('&');
		var pair;
		for (var i = 0; i < arr.length; i++) {
			pair = arr[i].split('=');
			vars.push(pair[0]);
			vars[pair[0]] = unescape(pair[1]);
		}
		return vars;
	}

    jQuery("#AuthorizeNetPopupInner").dialog({
        modal: true,
        autoOpen: true,
        closeOnEscape: false,
        draggable: false,    
        width: 'auto',
        height: 'auto',
        title: $popupTitle
    });
    jQuery("form#formAuthorizeNetPopup").submit();
} ());
</script>

CUT;
        $this->_response->setBody($this->view->render('layout.phtml'));
    }
    
    /**
     * Output special html so authorize.net popup may communicate with our page
     * via javascript
     */
    function iframeAction()
    {
        $this->_response->setBody( <<<CUT
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IFrame Communicator</title>
<script type="text/javascript">
//<![CDATA[
function callParentFunction(str) {
  if (str && str.length > 0 && window.parent && window.parent.parent 
    && window.parent.parent.AuthorizeNetPopup && window.parent.parent.AuthorizeNetPopup.onReceiveCommunication)
  {
    window.parent.parent.AuthorizeNetPopup.onReceiveCommunication(str);
    // If you get an error with this line, it might be because the domains are not an exact match (including www).
  }
}

function receiveMessage(event) {
  if (event && event.data) {
    callParentFunction(event.data);
  }
}

if (window.addEventListener) {
  window.addEventListener("message", receiveMessage, false);
} else if (window.attachEvent) {
  window.attachEvent("onmessage", receiveMessage);
}

if (window.location.hash && window.location.hash.length > 1) {
  callParentFunction(window.location.hash.substring(1));
}
//]]>
</script>
</head>
<body>
</body>
</html>
CUT
        ); // setBody
    }    
}
